﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Stats.Distribution
{
    /// <summary>
    /// pValue code converted from Javascript source http://www.fourmilab.ch/rpkp/experiments/analysis/zCalc.html?
    /// </summary>
    public class StandardNormal
    {
        private const Double Z_Max = 6.0;

        public List<Double> Values { get; set; }

        public StandardNormal()
        {
        }

        public StandardNormal(List<Double> Values)
        {
            this.Values = Values;
        }

        /// <summary>
        /// Find mean value.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static Double Mean(List<Double> Values)
        {
            return Values.Average();
        }

        /// <summary>
        /// Find medium of the list of numbers.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static Double Medium(List<Double> Values)
        {
            Values.Sort();
            Int32 Middle = Convert.ToInt32(Values.Count / 2);

            return Values[Middle];
        }

        /// <summary>
        /// Calculate the variance of the list of numbers.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static Double Varience(List<Double> Values)
        {
            Double MeanValue = Mean(Values);

            Double DiffernceTotal = 0.0;
            foreach (Double Value in Values)
            {
                Double Difference = Value - MeanValue;
                Double DifferenceSquared = Difference * Difference;
                DiffernceTotal += DifferenceSquared;
            }

            return DiffernceTotal / Values.Count;
        }

        /// <summary>
        /// Calculate the standard deviation of the list of numbers.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static Double StandardDeviation(List<Double> Values)
        {
            return Math.Sqrt(Varience(Values));
        }

        /// <summary>
        /// Calculate the standard deviation of the list of numbers.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static Double σ(List<Double> Values)
        {
            return Math.Sqrt(Varience(Values));
        }

        /// <summary>
        /// Calcuate z score.
        /// </summary>
        /// <param name="X"></param>
        /// <param name="Mean"></param>
        /// <param name="σ"></param>
        /// <returns></returns>
        public static Double z(Double X, Double Mean, Double σ)
        {
            return (X - Mean) / σ;
        }

        /// <summary>
        /// Calculate p value from z score.
        /// </summary>
        /// <param name="zScore"></param>
        /// <returns></returns>
        public static Double pValue(Double zScore)
        {
            return pValue(zScore, 4);
        }

        /// <summary>
        /// Calculate p value from z score.
        /// </summary>
        /// <param name="zScore"></param>
        /// <param name="decimals"></param>
        /// <returns></returns>
        public static Double pValue(Double zScore, Int32 decimals)
        {
            if (Math.Abs(zScore) > Z_Max)
            {
                throw new ArgumentOutOfRangeException("z value must be between -6 and 6.\nValues outside this range have probabilities\nwhich exceed the precision of calculation used in this page.");
            }
            else
            {
                return Math.Round(1 - poz(Math.Abs(zScore)), decimals);
            }
        }

        /// <summary>
        /// Adapted from a polynomial approximation in:  Ibbetson D, Algorithm 209
        /// Collected Algorithms of the CACM 1963 p. 616
        /// </summary>
        /// <param name="z"></param>
        /// <returns></returns>
        private static Double poz(Double z)
        {
            Double y = 0.0;
            Double x = 0.0;
            Double w = 0.0;

            if (z == 0.0)
            {
                x = 0.0;
            }
            else
            {
                y = 0.5 * Math.Abs(z);
            }
            if (y > (Z_Max * 0.5))
            {
                x = 1.0;
            }
            else if (y < 1.0)
            {
                w = y * y;
                x = ((((((((0.000124818987 * w
                         - 0.001075204047) * w + 0.005198775019) * w
                         - 0.019198292004) * w + 0.059054035642) * w
                         - 0.151968751364) * w + 0.319152932694) * w
                         - 0.531923007300) * w + 0.797884560593) * y * 2.0;
            }
            else
            {
                y -= 2.0;
                x = (((((((((((((-0.000045255659 * y
                               + 0.000152529290) * y - 0.000019538132) * y
                               - 0.000676904986) * y + 0.001390604284) * y
                               - 0.000794620820) * y - 0.002034254874) * y
                               + 0.006549791214) * y - 0.010557625006) * y
                               + 0.011630447319) * y - 0.009279453341) * y
                               + 0.005353579108) * y - 0.002141268741) * y
                               + 0.000535310849) * y + 0.999936657524;
            }

            return z > 0.0 ? ((x + 1.0) * 0.5) : ((1.0 - x) * 0.5);

        }
    }
}
